#include "GuideLayer.h"
#include <SEUi/SEUi.h>
#include <SECore/CoreApplication.h>
#include <SEEdit/Layer/EditLayer.h>
#include <SEngine/Layers/SceneLayer.h>
#include <SEPhysics/Layers/PhysicsLayer.h>
#include <SEAnimation/Layers/AnimationLayer.h>
#include <filesystem>
#include <SEWindow/Dialogs/OpenFileDialog.h>
#include <SEWindow/Dialogs/SaveFileDialog.h>
#include <SEDebug/Log/Log.h>
#include <fstream>
#include <sstream>
#include <SEEdit/Descriptors/AssetsDescriptor.h>
#include <SESerializeYAML/ProjectSerializer.h>
#include <SECore/Util.h>
#include <SEAssets/AssetsLoader.h>
#include <SECore/Manager.h>
#include <SECore/Config.h>

namespace SEngine
{
    void GuideLayer::Attach()
    {
    }

    void GuideLayer::Update(int delta)
    {
        ImGui::SetNextWindowPos(ImVec2{0.0f, 0.0f});
        ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);
        ImGui::Begin("Guide", 0, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
        if (ImGui::Button("Open Project")) { OpenProject(); }
        ImGui::SameLine();
        if (ImGui::Button("New Project")) { NewProject(); }
        ImGui::SameLine();
        ImGui::InputText("##ProjectName", m_opt.path.data(), m_opt.path.capacity());
        ImGui::SameLine();
        if (ImGui::Button("Go")) { Go(); }
        ImGui::Separator();
        if (m_showProgressBar) {
            ImGui::ProgressBar(m_loadProgress);
            ImGui::Text("Loading: %s", m_loadFileName.c_str());
        }
        ImGui::End();
    }

    void GuideLayer::Go()
    {
        std::filesystem::path path(m_opt.path);
        if (!std::filesystem::exists(path)) {
            Log::GetInstance().Error("File {} is not exists!", path.string());
            return;
        }   
        if (path.extension() != ".seproject") {
            Log::GetInstance().Error("File {} is not SEngine project!", path.string());
            return;
        }
        m_opt.name = path.filename().string();
        Project project(std::move(m_opt));
        Enter(project);
    }

    void GuideLayer::Enter(Project & prj)
    {
        AssetsDescriptor assetsDes;

        auto LoadAssets = [this](AssetsElem& curElem, AssetsLoader::LoaderTable & table) {
            CoreApplication::App().SendTask([&curElem, &table, this](auto&) {
                this->m_showProgressBar = true;
                this->m_loadProgress += (1.0f / this->m_loadProgressTotal);
                this->m_loadFileName = curElem.path;
                Log::GetInstance().Debug("{}", curElem.path);
                table.at(curElem.assetsTypeName)(curElem, curElem.rPath);
            }, EventLoop<CoreApplication&>::TaskMode::Defer);
        };

        auto LoadAssets2 = [this](AssetsElem& curElem, AssetsLoader::LoaderTable & table) {
            CoreApplication::App().SendTask([&curElem, &table, this](auto&) {
                this->m_showProgressBar = true;
                this->m_loadProgress += (1.0f / this->m_loadProgressTotal);
                this->m_loadFileName = curElem.path;
                Log::GetInstance().Debug("{}", curElem.path);
                table.at(curElem.assetsTypeName)(curElem, curElem.name.substr(0, curElem.name.find_last_of('.')));
            }, EventLoop<CoreApplication&>::TaskMode::Defer);
        };

        auto & config = Manager::GetInstance().Get<ConfigNode>("SEConfig").value().Value();

        auto enginePath = config.GetValue<ConfigNode::Object>()->get().at("Engine").GetValue<ConfigNode::Object>()->get().at("path").GetValue<std::string>()->get();
        std::filesystem::path root(m_opt.path);
        AssetsLoader loader(root.parent_path().string());
        // m_loadProgressTotal += loader.ScanFileTree(*assetsDes.engine, std::filesystem::path(CoreApplication::ApplicationCwd()).parent_path().parent_path().append("Engine").make_preferred().string(), LoadAssets2);
        m_loadProgressTotal += loader.ScanFileTree(*assetsDes.engine, enginePath, LoadAssets2);
        m_loadProgressTotal += loader.ScanFileTree(*assetsDes.assets, root.parent_path().append("Assets").make_preferred().string(), LoadAssets);

        m_app.AddDescriptor<Project>(std::move(prj));
        m_app.AddDescriptor<AssetsDescriptor>(std::move(assetsDes));
        
        m_app.SendTask([this](CoreApplication & app) {
            this->m_showProgressBar = false;
            this->InitLayers(app);
            std::string projFilePath = app.GetDescriptor<Project>()->get().GetOption().path;
            app.GetDescriptor<Project>()->get().Deserialize(SEReadText(projFilePath));
            app.RemoveLayer(this->Name());
        }, EventLoop<SEngine::CoreApplication &>::TaskMode::Defer);
    }

    void GuideLayer::OpenProject()
    {   
        OpenFileDialog dialog("Open Project");
        dialog.AddFileType("SE Project(*.seproject)", "*.seproject");
        dialog.Show();
        if (!dialog.HasSucceeded()) {
            return;
        }
        m_opt.path = std::filesystem::path(dialog.GetSelectedFilePath()).make_preferred().string();
        m_opt.name = dialog.GetSelectedFileName();
        Project project(std::move(m_opt));
        Enter(project);
    }

    void GuideLayer::NewProject()
    {
        SaveFileDialog dialog("New Project");
        dialog.DefineExtension("SE Project(*.seproject)", "");
        dialog.Show();
        if (!dialog.HasSucceeded()) {
            return;
        }
        std::filesystem::path path(dialog.GetSelectedFilePath());
        std::filesystem::create_directory(path.parent_path().append(path.filename().string()));
        std::filesystem::create_directory(path.parent_path().append(path.filename().string()).append("Assets"));
        std::filesystem::create_directory(path.parent_path().append(path.filename().string()).append("Scripts"));
        m_opt.name = path.filename().string() + ".seproject";
        m_opt.path = path.append(m_opt.name).make_preferred().string();
        Project project(std::move(m_opt));
        Enter(project);
        SEWriteText(m_opt.path, project.Serialize());
    }

    void GuideLayer::InitLayers(CoreApplication & app)
    {
        auto scene = app.GetDescriptor<Project>()->get().GetScene();
        {
            auto layer = std::make_unique<AnimationLayer>(app);
            scene->AddComponentSignal.AddSlot([layer = layer.get()](IComponent& comp) { layer->SignalAddComponent.Invoke(comp); return SlotState::Keep; });
            scene->RemoveComponentSignal.AddSlot([layer = layer.get()](IComponent& comp) { layer->SignalRemoveComponent.Invoke(comp); return SlotState::Keep; });
            app.PushBackLayer("AnimationLayer", std::move(layer));
        }
        {
            auto layer = std::make_unique<SceneLayer>(app);
            app.PushBackLayer("SceneLayer", std::move(layer));
        }
        {
            auto layer = std::make_unique<PhysicsLayer>(app);
            scene->AddComponentSignal.AddSlot([layer = layer.get()](IComponent& comp) { layer->SlotAddComponent(comp); return SlotState::Keep; });
            scene->RemoveComponentSignal.AddSlot([layer = layer.get()](IComponent& comp) { layer->SlotRemoveComponent(comp); return SlotState::Keep; });
            app.PushBackLayer("PhysicsLayer", std::move(layer));
        }
        {
            auto layer = std::make_unique<EditLayer>(app);
            app.PushBackLayer("EditLayer", std::move(layer));
        }
    }

} // namespace SEngine
